package com.gzsxy.esjy.bus.log.aspect;


import com.alibaba.fastjson.JSON;
import com.gzsxy.esjy.bus.log.annotation.OperLog;

import com.gzsxy.esjy.bus.service.IOperlogService;
import com.gzsxy.esjy.common.base.util.FileUtils;
import com.gzsxy.esjy.common.base.util.RequestHolder;
import com.gzsxy.esjy.common.base.util.RequestUtil;
import com.gzsxy.esjy.common.base.util.ThreadPoolUtil;
import com.gzsxy.esjy.redis.user.ISystemUser;
import com.gzsxy.esjy.service.base.DictionaryConstants;
import com.gzsxy.esjy.service.base.dto.request.OperlogRequest;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.Enumeration;


/**
 * @author xiaolong
 * @version 1.0
 * @description: 切片的实现 监控controller的请求信息和响应信息
 * @date 2022/4/17 15:55
 */
@Aspect //标识切面
@Slf4j
@Component
@ConditionalOnProperty(name = "gzsxy.aspect.enable", havingValue = "true")
public class WebLogAspect {

    @Resource
    private IOperlogService operlogService;

    @Autowired
    private ISystemUser iSystemUser;

    private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal beginTime");



    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码（触发条件）
     */
    @Pointcut("@annotation(com.gzsxy.esjy.bus.log.annotation.OperLog)")
    public void operLogPoinCut() {
    }



    /**
     * 配置环绕通知,使用在方法logPointcut()上注册的切入点
     *
     * @param joinPoint join point for advice
     */
    @Before(value = "operLogPoinCut()")
    public void doBefore(JoinPoint joinPoint) {
        //线程绑定变量（该数据只有当前请求的线程可见）
        Date beginTime = new Date();
        beginTimeThreadLocal.set(beginTime);
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        if (request != null) {
            // 记录下请求内容
            log.info("URL : {}", request.getRequestURL().toString());
            log.info("HTTP_METHOD :{} ", request.getMethod());
            log.info("IP : {}", request.getRemoteAddr());
            log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
            log.info("ARGS : " + FileUtils.obiectToJson(joinPoint.getArgs()));
            Enumeration<String> enu = request.getParameterNames();
            while (enu.hasMoreElements()) {
                String name = enu.nextElement();
                log.info("name:{},value:{}", name, request.getParameter(name));
            }

        } else {
            log.error("ServletRequestAttributes为空！");
        }
    }





    /**
     * 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     *
     * @param point 切入点
     * @param operLog         异常信息
     */
    @Around("@annotation(operLog)")
    @SneakyThrows
    public Object saveExceptionLog(ProceedingJoinPoint point, OperLog operLog) {

        Object obj;
        OperlogRequest operlogRequest = this.getOperlog(point,operLog);
        try {
            //执行指定切面方法
            obj = point.proceed();
            //请求开始时间
            long beginTime = beginTimeThreadLocal.get().getTime();
            long endTime = System.currentTimeMillis();
            //请求耗时
            Long logElapsedTime = endTime - beginTime;
            operlogRequest.setCostTime(logElapsedTime);
            operlogRequest.setResponseParam(JSON.toJSONString(obj));
            operlogRequest.setStatus(DictionaryConstants.NUMBER_1);

        } catch (Exception e) {
            operlogRequest.setErrorName(e.getClass().getName()); // 异常名称
            operlogRequest.setErrorMessage(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));
            operlogRequest.setStatus(DictionaryConstants.NUMBER_2);
            throw e;
        } finally {
            //成功执行成功入库 持久化(存储到数据或者ES，可以考虑用线程池)
            ThreadPoolUtil.getPool().execute(new SaveSystemLogThread(operlogRequest, operlogService));
        }
        return obj;
    }

    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuilder strbuff = new StringBuilder();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet).append("\n");
        }
        return exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
    }

    /**
     * 保存日志至数据库
     */
    private static class SaveSystemLogThread implements Runnable {

        private OperlogRequest log;
        private IOperlogService operlogService;

        public SaveSystemLogThread(OperlogRequest olLog, IOperlogService operlogService) {
            this.log = olLog;
            this.operlogService = operlogService;
        }

        @Override
        public void run() {
            operlogService.addOperlog(log);
        }
    }



    /**
     * @description: 封装操作请求
     * @param
     * @return
     */
    public OperlogRequest getOperlog(ProceedingJoinPoint point, OperLog opLog){
        OperlogRequest operlogRequest = new OperlogRequest();
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        if (request != null) {

            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) point.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取请求的类名
            String className = point.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName;
            // 获取操作
//            OperLog opLog = method.getAnnotation(OperLog.class);
            if (opLog != null) {
                String operModul = opLog.operModul();
                String operType = FileUtils.obiectToJson(opLog.operType());
                String operDesc = opLog.operDesc();
                // 操作模块
                operlogRequest.setOperModel(operModul);
                // 操作类型
                operlogRequest.setOperType(operType);
                // 操作描述
                operlogRequest.setOperDesc(operDesc);
            }

            if (ObjectUtils.isEmpty(request.getParameterMap())){
                //json传参 post请求
                operlogRequest.setRequestParam(FileUtils.obiectToJson(point.getArgs()));
            } else {
                // request.getParameterMap()请求参数
                // 将参数所在的数组转换成json
                operlogRequest.setRequestParam(FileUtils.obiectToJson(request.getParameterMap()));
            }

            operlogRequest.setMethod(methodName); // 请求方法名
            // 操作URI
            operlogRequest.setUri(request.getRequestURI());
            // 操作员IP
            operlogRequest.setIp(RequestUtil.getClientIp(request));
            // 发生异常时间
            operlogRequest.setCreateTime(new Date());

            operlogRequest.setOperUserId((String) iSystemUser.get("id",""));
            operlogRequest.setOperUserName((String) iSystemUser.get("userName",""));

        } else {
            log.error("ServletRequestAttributes为空！");
        }
        return operlogRequest;
    }

}
